// SerialApp.cpp : Defines the entry point for the console application.
//
// Can be used as simple test for OS launched as can be run before the OS shell (not to be confused with Shell.exe which is Target Control).
// Sends some characters down serial port
//
// See haeder file for settings
//
// This app only requires device.exe, ie for the serial port to be loaded.
// Can load before GWES (typically 30) and MINSHELL (typically 80)

#include "stdafx.h"
#include "SerialApp.h"





HANDLE hWritePort;
HANDLE hReadPort;
WCHAR lpszWritePortName[] = DEFAULT_COMPORT;
WCHAR lpszReadPortName[] =  DEFAULT_COMPORT;
char gMessage[] = MESSAGE;
BOOL gUsingSamePort = TRUE;
DWORD gPortWriteNo = DEFAULT_COMPORTNO;
DWORD gPortReadNo = DEFAULT_COMPORTNO;
DWORD gBAUDRate = DEFAULT_BAUDRATE;
LONG gTestTime = DEFAULT_TESTTIME;
DWORD gPacketSizeMax = DEFAULT_PACKETSIZE;
DWORD gPacketSizeMin = DEFAULT_PACKETSIZE;
BOOL gFixedPacketSize = TRUE;
LONG gWriteTimeout = DEFAULT_WRITETIMEOUT;
LONG gReadTimeout = DEFAULT_READTIMEOUT;
BOOL gAbortOnError = DEFAULT_ABORT_ON_ERROR;
BOOL gReadAutoMode = DEFAULT_READAUTOMODE;
DWORD gNumReadLoops = 0;
DWORD gNumReads;
DWORD gBytesRead;
DWORD gDisplayPeriod = DEFAULT_DISPLAYPERIOD;
DWORD gDisplayPeriodTix = 0;
DWORD gNumErrors = 0;
DWORD gTotalNumReads = 0;





int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{

	BOOL res = true;

	LPSYSTEMTIME now;
	DWORD startTime;

	BOOL success = true;

	DWORD cmdlineLength = argc;
	DWORD paramNo = 1;

	LONG LoopNo = 0;
	DWORD charsTosend;
	LONG  mode = GENERIC_READ| GENERIC_WRITE;
	BOOL atStart = TRUE;

	DWORD dwCurrentTix = 0;


	//All command line parameters are optional but if one anyone is used all "lower" ones must be present.
	//SerialApp  <Write Com port No> <Read Com port No> <BAUD> <PacketSizeMax> <PacketSizeMax> <Test Time> <Write Timeout> <Read Timeout> <Use Auto ReadTimeout>
	//Check for index no. Only simple brute force error catching on _ wtoi() (try .. catch)

	try
	{
		if (cmdlineLength > paramNo)
		{
			gPortWriteNo = _wtoi(argv[paramNo++]);
			lpszWritePortName[wcslen(lpszWritePortName) - 1] = (WCHAR)(gPortWriteNo + (DWORD) '0');
		}
		if (cmdlineLength > paramNo)
		{
			gPortReadNo = _wtoi(argv[paramNo++]);
			if (gPortWriteNo != gPortReadNo)
			{
				lpszWritePortName[wcslen(lpszReadPortName) - 1] = (WCHAR)(gPortReadNo + (DWORD) '0');
				gUsingSamePort = FALSE;
			}
		}
		if (cmdlineLength > paramNo)
		{
			gBAUDRate = _wtoi(argv[paramNo++]);
		}

		if (cmdlineLength > paramNo)
		{
			DWORD ps = _wtoi(argv[paramNo++]);
			if ((ps > 0) && (ps <= MAX_PACKET_SIZE))
				gPacketSizeMax = ps;
		}
		if (cmdlineLength > paramNo)
		{
			gPacketSizeMin = _wtoi(argv[paramNo++]);
			if (gPacketSizeMin < 1)
				gPacketSizeMin = 1;
			if (gPacketSizeMin < gPacketSizeMax)
			{
					gFixedPacketSize = FALSE;
			}
		}
		if (cmdlineLength > paramNo)
		{
			gTestTime = _wtoi(argv[paramNo++]);
			if (gTestTime == 0)
				gTestTime = INFINITE;
		}
		if (cmdlineLength > paramNo)
		{
			gWriteTimeout = _wtoi(argv[paramNo++]);
			if (gWriteTimeout > 10000)
				gWriteTimeout = INFINITE;
		}
		if (cmdlineLength > paramNo)
		{
			gWriteTimeout = _wtoi(argv[paramNo++]);
			if (gWriteTimeout > 10000)
				gWriteTimeout = INFINITE;
		}
		if (cmdlineLength > paramNo)
		{
			DWORD dwReadAutoMode = _wtoi(argv[paramNo++]);
			if (dwReadAutoMode > 0)
				gReadAutoMode = TRUE;
		}
		if (cmdlineLength > paramNo)
		{
			DWORD dwAbortOnError = _wtoi(argv[paramNo++]);
			if (dwAbortOnError > 0)
				gAbortOnError = TRUE;
		}

	}
	catch (...)
	{
		wprintf(L"Invalid parameter No %d %s", paramNo - 1, NL);
		wprintf(L"Parameters: 	<Write Com port No> <Read Com port No> <BAUD> <PacketSizeMax> <PacketSizeMin> <Test Time> <Write Timeout> <Read Timeout> <Use Auto ReadTimeout> <AbortOnError> %s",NL);
		wprintf(L"Eg Defaults: 	        1                   1          115200       8                8          Inifinite    1000 (>10000=INFINITE) 1000          0  (False)       0 (False) %s",NL);
	}


	
	now = (LPSYSTEMTIME)malloc(sizeof(SYSTEMTIME));
	now->wDay = 8;
	now->wHour = 21;
	now->wMinute = 0;
	now->wSecond = 0;
	now->wMilliseconds = 0;
	now->wMonth = 9;
	now->wYear = 2014;
	SetLocalTime(now);

	if (PRINTLEVEL > 0)
		wprintf(L"+SerialApp:[1/5] Running SerialApp forever.%s",NL);

	mode = GENERIC_WRITE;
	if (gUsingSamePort)
		mode = GENERIC_READ | GENERIC_READ;
	hWritePort = SerialOpen(lpszWritePortName, mode);

	if (hWritePort != NULL)
	{
		hReadPort = hWritePort;
		if (SerialConfigure(lpszWritePortName, hWritePort))
		{
			res = true;
			if (!gUsingSamePort)
			{
				mode = GENERIC_READ;
				res = FALSE;
				hReadPort = SerialOpen(lpszReadPortName, mode);
				if (hReadPort != NULL)
				{
					if (SerialConfigure(lpszReadPortName, hReadPort))
					{
						res = TRUE;
					}
				}
			}
			startTime = GetTickCount();
			gDisplayPeriodTix = GetTickCount();
			gNumReadLoops = 0;
			gNumReads = 0;
			gBytesRead = 0;
			LoopNo = 0;
			gNumErrors = 0;
			gTotalNumReads = 0;
			while (res)
			{
				if (gTestTime != INFINITE)
				{
					if ((GetTickCount() - startTime) > (DWORD)gTestTime)
						break;
				}
				GetLocalTime(now);

				dwCurrentTix = GetTickCount();

				if ((dwCurrentTix - gDisplayPeriodTix > gDisplayPeriod) || (atStart) || (dwCurrentTix < gDisplayPeriodTix))
				{
					if (PRINTLEVEL > 1)
					if (gPacketSizeMax == gPacketSizeMin)
						wprintf(L"Sending packets size %d [Loops:%10d] %02d:%02d:%02d Total No:[Reads: %d / Incorrect Reads %d], Since last [NumReads %d Num Bytes Read %d NumReadLoops %d]%s", gPacketSizeMax, LoopNo, now->wHour, now->wMinute, now->wSecond, gTotalNumReads, gNumErrors, gNumReads, gBytesRead, gNumReadLoops,NL);
					else
						wprintf(L"Sending packets sizes %d to %d [Loops:%10d] %02d:%02d:%02d NumReads, %d Num Bytes Read %d, NumReadLoops %d%s", gPacketSizeMin, gPacketSizeMax, LoopNo, now->wHour, now->wMinute, now->wSecond, gNumReads, gBytesRead, gNumReadLoops,NL);

					gDisplayPeriodTix = GetTickCount();
					gNumReadLoops = 0;
					gTotalNumReads += gNumReads;
					gNumReads = 0;
					gBytesRead = 0;
					atStart = FALSE;
				}
				for (charsTosend = gPacketSizeMin; charsTosend <= gPacketSizeMax; charsTosend++)
				{
					GetLocalTime(now);
					LoopNo++;
					if (PRINTLEVEL > 2)
						wprintf(L"%d to %d [%6d] %02d:%02d:%02d%s", gPacketSizeMin, gPacketSizeMax, LoopNo, now->wHour, now->wMinute, now->wSecond,NL);
					res = WriteMessage((DWORD)charsTosend);
					if (!res)
					{
						wprintf(L"Serial Write Error.%s",NL);
						break;
					}

					res = SerialRead((DWORD)charsTosend);
					if (!res)
					{
						wprintf(L"Serial Read Error.%s",NL);
						break;
					}
				}

			}
			if (!gUsingSamePort)
			{
				SerialClose(lpszReadPortName, hReadPort);
			}
			SerialClose(lpszWritePortName, hWritePort);
		}
	}
	if (PRINTLEVEL > 0)
		wprintf(L"-SerialApp:[5/5] SerialApp Done%s",NL);
	return 0;
}


HANDLE SerialOpen(LPCWSTR port, LONG  mode)
{
	DWORD dwError;
	HANDLE hPort;
	if (PRINTLEVEL > 2)
		wprintf(L" +SerialApp: Openning %s.%s",port,NL);
	// Open the serial port.
	hPort = CreateFile(port,  // Pointer to the name of the port
		GENERIC_READ | GENERIC_WRITE, // Access (read-write) mode
		0,            // Share mode
		NULL,         // Pointer to the security attribute
		OPEN_EXISTING,// How to open the serial port
		0,            // Port attributes
		NULL);        // Handle to port with attribute
	if (hPort == INVALID_HANDLE_VALUE)
	{
		dwError = GetLastError();
		wprintf(L" -Serial Open %s Error # : %d%s",port,dwError,NL);
		//System Errors by number: http://msdn.microsoft.com/en-us/library/aa450919.aspx
		return NULL;
	}
	if (PRINTLEVEL > 2)
		wprintf(L" -SerialApp: Serial Port %s Openned OK.%s)",port,NL);
	return hPort;

}

void SerialClose(LPCWSTR port, HANDLE hPort)
{
	if (PRINTLEVEL > 2)
		wprintf(L" +SerialApp: Closing Serial Port.%s",port,NL);
	CloseHandle(hPort);
	if (PRINTLEVEL > 2)
		wprintf(L" -SerialApp: Serial Port %s Closed.%s",port,NL);
}

BOOL SerialConfigure(LPCWSTR port, HANDLE hPort)
{
	DWORD dwError;
	BOOL res;
	DCB PortDCB;
	if (PRINTLEVEL > 2)
		wprintf(L" +SerialApp: Configuring Serial Port %s.%s", port,NL);
	// Initialize the DCBlength member. 
	PortDCB.DCBlength = sizeof (DCB);

	// Get the default port setting information.
	GetCommState(hPort, &PortDCB);

	// Change the DCB structure settings.
	PortDCB.BaudRate = gBAUDRate;              // Current baud 
	PortDCB.fBinary = TRUE;               // Binary mode; no EOF check 
	PortDCB.fParity = FALSE;               // Enable parity checking 
	PortDCB.fOutxCtsFlow = FALSE;         // No CTS output flow control 
	PortDCB.fOutxDsrFlow = FALSE;         // No DSR output flow control 
	PortDCB.fDtrControl = DTR_CONTROL_DISABLE;
	// DTR flow control type 
	PortDCB.fDsrSensitivity = FALSE;      // DSR sensitivity 
	PortDCB.fTXContinueOnXoff = TRUE;     // XOFF continues Tx 
	PortDCB.fOutX = FALSE;                // No XON/XOFF out flow control 
	PortDCB.fInX = FALSE;                 // No XON/XOFF in flow control 
	PortDCB.fErrorChar = FALSE;           // Disable error replacement 
	PortDCB.fNull = FALSE;                // Disable null stripping 
	PortDCB.fRtsControl = RTS_CONTROL_DISABLE;
	// RTS flow control 
	PortDCB.fAbortOnError = gAbortOnError;        // Do not abort reads/writes on 
	// error
	PortDCB.ByteSize = 8;                 // Number of bits/byte, 4-8 
	PortDCB.Parity = NOPARITY;            // 0-4=no,odd,even,mark,space 
	PortDCB.StopBits = ONESTOPBIT;        // 0,1,2 = 1, 1.5, 2 

	// Configure the port according to the specifications of the DCB 
	// structure.
	if (!SetCommState(hPort, &PortDCB))
	{
		// Could not configure the serial port.
		dwError = GetLastError();
		wprintf(L" -SerialApp: Serial Configure %d Error # : %d.%s",dwError, port,NL);
		return FALSE;
	}	

	res = SetTimeouts( port,  hPort);
	if (!res)
	{
		return FALSE;
	}
	if (PRINTLEVEL > 2)
		wprintf(L" -SerialApp: Serial Port %d Configured.%s", port,NL);
	return TRUE;
}

BOOL SetTimeouts(LPCWSTR port, HANDLE hPort)
{
	DWORD dwError;
	COMMTIMEOUTS CommTimeouts;
	if (PRINTLEVEL > 2)
		wprintf(L" +SerialApp: Configuring Serial Port %s Timeouts.%s", port,NL);
	//http://msdn.microsoft.com/en-us/library/ee481885(v=winembedded.60).aspx
	// Retrieve the timeout parameters for all read and write operations
	// on the port. 

	GetCommTimeouts(hPort, &CommTimeouts);

	// Change the COMMTIMEOUTS structure settings.
	//////////////////////////////////////////////
	
	/*ReadIntervalTimeoutSpecifies the maximum acceptable time, in milliseconds, to elapse between the arrival of two characters on the communication line.
		In Windows CE, during a ReadFile operation, the time period begins immediately.
		If the interval between the arrivals of two characters exceeds the time amount specified in ReadIntervalTimeout, the ReadFile operation is completed and buffered data is returned.
		A value of zero indicates that interval timeouts are not used.*/
	CommTimeouts.ReadIntervalTimeout = 0; // MAXDWORD;

	/* ReadTotalTimeoutMultiplierSpecifies the multiplier, in milliseconds, used to calculate the total timeout period for read operations.
		For each read operation, this value is multiplied by the requested number of bytes to be read.*/
	CommTimeouts.ReadTotalTimeoutMultiplier = 1;
	
	/*ReadTotalTimeoutConstantSpecifies the constant, in milliseconds, used to calculate the total timeout period for read operations.
		For each read operation, this value is added to the product of the ReadTotalTimeoutMultiplier member and the requested number of bytes.
		A value of zero for the ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant members indicates that total timeouts are not used for read operations.*/
	CommTimeouts.ReadTotalTimeoutConstant = gReadTimeout; // 0;

	/*WriteTotalTimeoutMultiplierSpecifies the multiplier, in milliseconds, used to calculate the total timeout period for write operations.
		For each write operation, this value is multiplied by the number of bytes to be written.*/
	CommTimeouts.WriteTotalTimeoutMultiplier = 1;

	/*WriteTotalTimeoutConstantSpecifies the constant, in milliseconds, used to calculate the total timeout period for write operations.
		For each write operation, this value is added to the product of the WriteTotalTimeoutMultiplier member and the number of bytes to be written.
		A value of zero for the WriteTotalTimeoutMultiplier and WriteTotalTimeoutConstant members indicates that total timeouts are not used for write operations.*/
	CommTimeouts.WriteTotalTimeoutConstant = gWriteTimeout; // 1000;

	/*If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to MAXDWORD and sets ReadTotalTimeoutConstant to a value greater than zero and less than MAXDWORD, one of the following occurs when the ReadFile function is called:
		If there are characters in the input buffer, ReadFile returns immediately with the characters in the buffer.
		If there are no characters in the input buffer, ReadFile waits until a character arrives and then returns immediately.
		If no characters arrive within the time specified by ReadTotalTimeoutConstant, ReadFile times out.
		*/
	if (gReadAutoMode)
	{
		CommTimeouts.ReadIntervalTimeout = MAXDWORD;
		CommTimeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
	}

	// Set the timeout parameters for all read and write operations
	// on the port. 
	if (!SetCommTimeouts(hPort, &CommTimeouts))
	{
		dwError = GetLastError();
		wprintf(L" -SerialApp: Set Timeouts %s Error # : %d.%s", dwError, port,NL);
		return FALSE;
	}
	if (PRINTLEVEL > 2)
		wprintf(L" -SerialApp: Serial Port %s Timeout Configured.%s", port,NL);
	return TRUE;
}

BOOL SerialWrite(BYTE * AtByte, DWORD len)
{
	DWORD dwError,
		dwNumBytesWritten;
	if (PRINTLEVEL > 3)
		wprintf( L" +SerialApp: Writing %5d Byte/s.%s",len,NL);

	if (!WriteFile(hWritePort,   // Port handle
		AtByte,             // Pointer to the data to write 
		len,                // Number of bytes to write
		&dwNumBytesWritten, // Pointer to the number of bytes written
		NULL                // Using Nonoverlapped (Sychronous write)
		))
	{
		dwError = GetLastError();
		wprintf(L" -SerialApp: Serial Write Error # : %d.%s",dwError,NL);
		return FALSE;
	}
	if (PRINTLEVEL > 3)
		wprintf(L" -SerialApp %5d Byte/s wrttten.%s", dwNumBytesWritten,NL);
	return TRUE;

}

BOOL WriteMessage( DWORD len)
{
	return SerialWrite((BYTE *)gMessage, len);
}


//Note this is a synchronous read of one byte.
//ie Is not event driven.
//Its a busy wait loop until a char is received.
BOOL  SerialRead(DWORD len)
{
	DWORD dwError;
	DWORD numErrors;
	BYTE Byte[100];
	DWORD count = 0;
	DWORD numLoops = 0;
	DWORD dwBytesTransferred=0;
	DWORD startTix, elapsedTix;
	startTix = GetTickCount();
	if (PRINTLEVEL > 3)
		wprintf(L" +SerialApp: Reading %5d bytes.%s",len,NL);
	//while (dwBytesTransferred==0) 
	gNumReads++;
	while ((count <len) && ((elapsedTix= GetTickCount() - startTix) < 10000))

	{
		gNumReadLoops++;
		numLoops++;
		if (!ReadFile(hReadPort,                // Port handle
			&Byte,                // Pointer to data to read
			len,                    // Number of bytes to read
			&dwBytesTransferred,  // Pointer to number of bytes
			// read
			NULL                  // Must be NULL for Windows CE
			))

		{
			dwError = GetLastError();
			wprintf(L"-SerialApp: Serial Read Error # : %d Num bytes recvd: %d out of %d requested. Num reads: %d. Last dwBytesTransferred %d.%s", dwError, count, len, numLoops, dwBytesTransferred,NL);
			wprintf(L"Packets size %d  Total No:[Reads: %d / Incorrect Reads %d], Since last [NumReads %d Num Bytes Read %d NumReadLoops %d]%s", gPacketSizeMax,  gTotalNumReads, gNumErrors, gNumReads, gBytesRead, gNumReadLoops, NL);

			return FALSE;
		}
		count += dwBytesTransferred;
		gBytesRead += dwBytesTransferred;
	}
	if (elapsedTix >= 10000)
	{
		wprintf(L"-SerialApp: Serial Read Time out. Num bytes recvd: %d out of %d requested. Num reads: %d.  Last dwBytesTransferred %d.%s",  count, len, numLoops, dwBytesTransferred, NL);
		wprintf(L"Packets size %d Total No:[Reads: %d / Incorrect Reads %d], Since last [NumReads %d Num Bytes Read %d NumReadLoops %d]%s", gPacketSizeMax, gTotalNumReads, gNumErrors, gNumReads, gBytesRead, gNumReadLoops, NL);

		return FALSE;
	}
	numErrors = 0;
	for (DWORD i = 0; i < len; i++)
	{
		if (Byte[i] != (BYTE)gMessage[i])
		{
			numErrors++;
			gNumErrors++;
		}
	}
	if (numErrors != 0)
		wprintf(L"============= Read != written. Num errors = %d =========%s", numErrors,NL);
	if (PRINTLEVEL > 3)
		wprintf(L" =SerialApp: %5d byte/s Read.%s",dwBytesTransferred,NL );
	return true;

}


